/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */

#include "ns3/netanim-module.h"
#include "ns3/csma-module.h"
#include "ns3/aodv-module.h"
#include "ns3/gpsr-module.h"
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/wifi-module.h"
#include "ns3/v4ping-helper.h"
#include "ns3/on-off-helper.h"
#include "ns3/udp-echo-server.h"
#include "ns3/udp-echo-client.h"
#include "ns3/udp-echo-helper.h"
#include "ns3/internet-stack-helper.h"
#include <iostream>
#include <cmath>

#include "ns3/propagation-delay-model.h"
#include "ns3/propagation-loss-model.h"

using namespace ns3;

double last_sec[271];

class GpsrExample
{
public:
	GpsrExample ();
	/// Configure script parameters, \return true on successful configuration
	bool Configure (int argc, char **argv);
	/// Run simulation
	void Run ();
	/// Report results
	void Report (std::ostream & os);
	void CourseChange (std::string context, Ptr<const MobilityModel> mobility);
	void SetupPacketSend (uint32_t nodeid);
	void SetupPacketReceive (Ipv4Address addr, Ptr <Node> node, uint16_t port);
	void ReceivePacket (Ptr<Socket> socket);

private:
	///\name parameters
	//\{
	/// Number of nodes
	uint32_t size;
	/// Width of the Node Grid
	uint32_t gridWidth;
	/// Distance between nodes, meters
	double step;
	/// Simulation time, seconds
	double totalTime;
	/// Write per-device PCAP traces if true
	bool pcap;
	/// Print routes if true
	bool printRoutes;
	/// Packet size (Bytes)
	uint32_t pkt_size;
	/// Inter-Packet Interval
	DataRate data_rate;
	/// Traffic density (available options: "low" or "high")
	string traffic_density;
	/// Test number (available options: "1.1", "1.2", "2.1", "2.2" or "3.1")
	string test_id;
	/// Simulation run ID
	uint32_t runID;
	string runID_str;
	uint32_t packetsRcvd[271];
	uint32_t packetsSent[271];
	double last_node_position[271];
	double last_node_vel[271];


	std::string coursechangelog; // Course Change Log File
	std::ofstream os_ccl;
	std::string finalcoursechangelog; // Course Change Log File - Final Positions
	std::ofstream os_fccl;

	//\}

	///\name network
	//\{


	NodeContainer nodes; //Nodes => WiFi Nodes
	NodeContainer ppnodes; // SOS RSU = node 1 ** Gantry RSU = node 0

	NetDeviceContainer devices;
	NetDeviceContainer ppdevices;

	Ipv4InterfaceContainer ppinterfaces;
	Ipv4InterfaceContainer interfaces;

	//\}

private:
	void CreateNodes ();
	void CreateDevices ();
	void InstallInternetStack ();
	void InstallApplications ();
};


int main (int argc, char **argv)
{
	GpsrExample test;
	if (! test.Configure(argc, argv))
		NS_FATAL_ERROR ("Configuration failed. Aborted.");


	test.Run ();
	test.Report (std::cout);
	return 0;
}

void
GpsrExample::CourseChange (std::string context, Ptr<const MobilityModel> mobility)
{
	NS_LOG_UNCOND("COURSECHANGE called..");

	size_t first_delimit = context.find_first_of ('/', 1);
	size_t second_delimit = context.find_first_of ('/', first_delimit + 1);
	std::string nodeid_str = context.substr (first_delimit + 1, second_delimit - 1 - first_delimit);
	stringstream ss (nodeid_str);
	uint32_t nodeid;
	ss >> nodeid;

	Ptr<Node> node0 = nodes.Get(0);
	Ptr<MobilityModel> mob0 = node0->GetObject<MobilityModel>();

	if (last_sec[nodeid] != floor (Simulator::Now ().GetSeconds () + 0.1))
	{
		std::cout << "----- " << Simulator::Now () << " " << last_sec[nodeid] << " " << nodeid << " " << mobility->GetPosition ().x << std::endl;

		if (mobility->GetPosition ().x == last_node_position[nodeid]) {
			std::cout << Simulator::Now () << " " << nodeid << " " << mobility->GetPosition ().x << " " << last_node_position[nodeid] << std::endl;
			Simulator::ScheduleNow (&GpsrExample::SetupPacketSend, this, nodeid);
		}
		last_node_position[nodeid] = mobility->GetPosition ().x;

		last_sec[nodeid] = floor (Simulator::Now ().GetSeconds () + 0.1);
	}
}
//-----------------------------------------------------------------------------

GpsrExample::GpsrExample () :

	size (5),
	totalTime (180),
	pcap (true),
	printRoutes (true),
	pkt_size (512),
	data_rate (40960), // initially, 8192 bps
	traffic_density ("low"),
	test_id ("1.1"),
	runID (3)
{
}

bool
GpsrExample::Configure (int argc, char **argv)
{
	// Enable GPSR logs by default. Comment this if too noisy
	LogComponentEnable("GpsrRoutingProtocol", LOG_LEVEL_ALL);

	Packet::EnablePrinting();

	CommandLine cmd;

	cmd.AddValue ("pcap", "Write PCAP traces.", pcap);
	cmd.AddValue ("printRoutes", "Print routing table dumps.", printRoutes);
	cmd.AddValue ("size", "Number of nodes.", size);
	cmd.AddValue ("time", "Simulation time, s.", totalTime);
	cmd.AddValue ("step", "Grid step, m", step);
	cmd.AddValue ("traffDensity", "Traffic density (low; high).", traffic_density);
	cmd.AddValue ("testID", "Test ID.", test_id);
	cmd.AddValue ("runID", "Simulation run number.", runID);
	cmd.Parse (argc, argv);

	SeedManager::SetSeed(12345);
	SeedManager::SetRun (runID);

	stringstream sstr;
	sstr << runID;
	runID_str = sstr.str();

	return true;
}

void
GpsrExample::Run ()
{
	//  Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", UintegerValue (1)); // enable rts cts all the time.
	CreateNodes ();
	CreateDevices ();
	InstallInternetStack ();
	InstallApplications ();

	GpsrHelper gpsr;
	gpsr.Install ();

	std::cout << "Starting simulation for " << totalTime << " s ...\n";

	coursechangelog = "/home/andre/workspace4/ns3-gpsr/traces/test1/logs/test1/course_changes_" + runID_str + ".log";

	finalcoursechangelog = "/home/andre/workspace4/ns3-gpsr/traces/test1/logs/final_course_changes_" + runID_str + ".log";

	// Opening logs files for output
	//std::ofstream os_ccl;
	os_ccl.open (coursechangelog.c_str());
	os_fccl.open (finalcoursechangelog.c_str ());

	// Connecting to trace sources
	Config::Connect ("/NodeList/*/$ns3::MobilityModel/CourseChange", MakeCallback (&GpsrExample::CourseChange, this));

	Simulator::Stop (Seconds (totalTime));
	//AnimationInterface anim ("animation.xml");
	Simulator::Run ();
	Simulator::Destroy ();
}

void
GpsrExample::Report (std::ostream &)
{
}

void
GpsrExample::CreateNodes ()
{
	std::cout << "Creating " << (unsigned)size << " nodes " << step << " m apart.\n";
	nodes.Create (size);
	// Name nodes
	for (uint32_t i = 0; i < size; ++i)
	{
		std::ostringstream os;
		os << "node-" << i;
		Names::Add (os.str (), nodes.Get (i));
	}

	// Sets the mobility model on each node
	std::string traceFile = "/home/andre/workspace4/ns3-gpsr/traces/test1/high-1.1-out.tcl";

	Ns2MobilityHelper mobility = Ns2MobilityHelper (traceFile);
	mobility.Install (); // configure movements for each node, while reading trace file


	//rsu_nodes = NodeContainer (nodes.Get (1), nodes.Get (2), nodes.Get (3)); // Comment this if only using V2V communication
//	for (uint32_t i = 0; i < size; ++i) {
		// uint32_t node = nodes.Get (i)->GetId ();
	//	Ptr<Node> node = nodes.Get (i);
	//	if (node->GetId () == 0 || node->GetId () == 1 || node->GetId () == 2)
	//	{
			//ppnodes.Add (node);
		//	std::cout << "Node " << node->GetId () << " added to RSU's" << std::endl;
	//	}

//	}

	//std::cout << "Nodes = " << nodes.GetN () << " | RSU nodes = " << ppnodes.GetN () << std::endl;

	//ppnodes = nodes.Get (0);


}

void
GpsrExample::SetupPacketReceive (Ipv4Address addr, Ptr <Node> node, uint16_t port)
{
	TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
	Ptr <Socket> sink = Socket::CreateSocket (node, tid);
	InetSocketAddress local = InetSocketAddress (addr, port);

	sink->Bind (local);
	sink->SetRecvCallback (MakeCallback (&GpsrExample::ReceivePacket, this));
}

void
GpsrExample::SetupPacketSend (uint32_t nodeid)
{
	std::cout << "Node " << nodeid << " started transmitting!" << std::endl;
	uint16_t port = 9;
	TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
	InetSocketAddress remote = InetSocketAddress (Ipv4Address ("255.255.255.255"), port);
	Ptr<Socket> source = Socket::CreateSocket (nodes.Get (nodeid), tid);
	source->SetAllowBroadcast (true);
	source->Connect (remote);

	OnOffHelper onoff ("ns3::UdpSocketFactory", Address (remote));
	onoff.SetAttribute ("OnTime", RandomVariableValue (ConstantVariable (1)));
	onoff.SetAttribute ("OffTime", RandomVariableValue (ConstantVariable (0)));
	onoff.SetAttribute ("PacketSize", UintegerValue (pkt_size));
	onoff.SetAttribute ("DataRate", DataRateValue (DataRate (data_rate)));

	ApplicationContainer app = onoff.Install (nodes.Get (nodeid));
	// app.Start (Simulator::Now()); // Accident time
	//app.Start (Seconds (96));
	//std::cout << double(96 + ((double)nodeid / nodes.GetN ())) << "|" << (double)nodeid << std::endl;
	app.Start (Seconds (96 + ((double)nodeid / nodes.GetN ())));
	app.Stop (Seconds (totalTime - 0.1));
}

void
GpsrExample::ReceivePacket (Ptr<Socket> socket)
{
	uint32_t nodeid = socket->GetNode ()->GetId ();
	Ptr <Packet> packet;

	Address from;
	while (packet = socket->RecvFrom(from)) {
		if (InetSocketAddress::IsMatchingType (from)) {
			InetSocketAddress address = InetSocketAddress::ConvertFrom (from);
			//std::cout << "Received " << packet->GetSize() << " bytes from " << address.GetIpv4() << std::enjYsTY6weQmdXjYsTY6weQmdXdl;
			if (address.GetIpv4() == "10.0.0.1")
				packetsRcvd[nodeid] = packetsRcvd[nodeid] + 1;
			// else
			// NS_LOG_UNCOND (nodeid << " received one packet.");
		}
	}
}


void
GpsrExample::CreateDevices ()
{
	NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();
	wifiMac.SetType ("ns3::AdhocWifiMac");

	YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
	YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
	wifiPhy.SetChannel (wifiChannel.Create ());
	WifiHelper wifi = WifiHelper::Default ();
	wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager", "DataMode", StringValue ("OfdmRate6Mbps"), "RtsCtsThreshold", UintegerValue (0));
	devices = wifi.Install (wifiPhy, wifiMac, nodes);



	if (pcap)
	{
		wifiPhy.EnablePcapAll (std::string ("gpsr"));
	}
}

void
GpsrExample::InstallInternetStack ()
{

	InternetStackHelper stack;



	Ipv4AddressHelper address;


	GpsrHelper gpsr;
	stack.SetRoutingHelper (gpsr);

	stack.Install (nodes);
	address.SetBase ("10.0.0.0", "255.255.255.0");
	interfaces = address.Assign (devices);


	for (uint32_t n = 0; n < nodes.GetN (); n++) {
		Ptr<Node> rsu = nodes.Get (n);
		Ptr<Ipv4> ipv4 = rsu->GetObject<Ipv4>();
		Ipv4InterfaceAddress iaddr = ipv4->GetAddress (1,0);
		Ipv4Address addri = iaddr.GetLocal ();
		std::cout << "NODE (" << rsu->GetId () << ") address: " << addri << std::endl;
	}


	if (printRoutes)
	{
		Ptr<OutputStreamWrapper> routingStream = Create<OutputStreamWrapper> ("aodv.routes", std::ios::out);
		// aodv.PrintRoutingTableAllAt (Seconds (8), routingStream);
		gpsr.PrintRoutingTableAllAt (Seconds (8), routingStream);
	}
}

void
GpsrExample::InstallApplications ()
{
	TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
	  uint32_t nodeid;
	  uint16_t port = 9;
	  InetSocketAddress remote = InetSocketAddress (Ipv4Address ("255.255.255.255"), port);

	  for (nodeid = 1; nodeid < nodes.GetN (); nodeid++) {
	    SetupPacketReceive (interfaces.GetAddress (nodeid), nodes.Get (nodeid), port);
	  }

	  for (nodeid = 4; nodeid < nodes.GetN (); nodeid++) {
	    //SetupPacketReceive (interfaces.GetAddress (nodeid), nodes.Get (nodeid), port);
	    Ptr<Socket> source = Socket::CreateSocket (nodes.Get (nodeid), tid);
	    source->SetAllowBroadcast (true);
	    source->Connect (remote);
	    SetupPacketSend (nodeid);
	  }

	  Ptr<Socket> source = Socket::CreateSocket (nodes.Get (0), tid);
	  source->SetAllowBroadcast (true);
	  source->Connect (remote);

	  //----------------------------------------------------//
	  // Create the OnOff application to send UDP datagrams //
	  //----------------------------------------------------//

	  OnOffHelper onoff ("ns3::UdpSocketFactory", Address (remote));
	  onoff.SetAttribute ("OnTime", RandomVariableValue (ConstantVariable (1)));
	  onoff.SetAttribute ("OffTime", RandomVariableValue (ConstantVariable (0)));
	  onoff.SetAttribute ("PacketSize", UintegerValue (pkt_size));
	  onoff.SetAttribute ("DataRate", DataRateValue (DataRate (data_rate)));
	  // onoff.SetAttribute ("MaxBytes", UintegerValue (pkt_size));

	  ApplicationContainer app = onoff.Install (nodes.Get (0));
	  app.Start (Seconds (10.0)); // Accident time
	  app.Stop (Seconds (totalTime - 0.1));

	  //----------------------------------------------------//
	  // Echo //
	  //----------------------------------------------------//



}

